package com.taglab.chic.search.impl;

import com.taglab.chic.search.Query;
import com.taglab.chic.search.SearchResults;
import com.taglab.chic.search.SearchService;
import com.taglab.chic.search.SearchServiceException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.ProxyHost;
import org.apache.commons.httpclient.methods.GetMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.xml.stream.XMLStreamException;
import java.io.InputStream;

/**
 * XXX implement support for validation
 * XXX Add caching
 * XXX Parse open search queries ie (http://example.com/?q={searchTerms})
 *
 * @author Markus Kobler
 */
public class OpenSearchService implements SearchService {

  private final static Logger logger = LoggerFactory.getLogger(OpenSearchService.class);

  private HttpConnectionManager connectionManager;

  private OpenSearchXMLParser openSearchXMLParser = new OpenSearchXMLParser();

  private String searchServiceUrl;

  private int serviceTimeout = 15 * 1000;

  private int maxConnections = 20;

  private ProxyHost proxy;


  public String getSearchServiceUrl() {
    return searchServiceUrl;
  }

  public void setSearchServiceUrl(String searchServiceUrl) {
    this.searchServiceUrl = searchServiceUrl;
  }

  public void setServiceTimeout(int serviceTimeout) {
    this.serviceTimeout = serviceTimeout;
  }

  public void setMaxConnections(int maxConnections) {
    this.maxConnections = maxConnections;
  }

  @PostConstruct
  public void initConnectionManager() {
    if( searchServiceUrl == null )  throw new NullPointerException("searchServiceUrl not set");
    
    connectionManager = new MultiThreadedHttpConnectionManager();
    connectionManager.getParams().setConnectionTimeout(serviceTimeout);
    connectionManager.getParams().setSoTimeout(serviceTimeout);
    connectionManager.getParams().setDefaultMaxConnectionsPerHost(maxConnections);
    connectionManager.getParams().setMaxTotalConnections(maxConnections);

    // currently default to system proxy settings
    if( System.getProperty("http.proxyHost") != null ) {

      int proxyPort = -1;
      try {
        proxyPort = Integer.parseInt(System.getProperty("http.proxyPort"));
      } catch( NumberFormatException nfe ) {
        logger.warn("Invalid proxy port", nfe);
      }

      proxy = new ProxyHost(System.getProperty("http.proxyHost"), proxyPort);
    }
    
  }
  

  public SearchResults search(Query query) throws SearchServiceException {

    try {
      
      HttpClient client = new HttpClient(connectionManager);

      if( proxy != null ) {
        client.getHostConfiguration().setProxyHost(proxy);
      }
      
      HttpMethod method = createRequest(query);
      
      int responseCode = client.executeMethod(method);

      if( HttpStatus.SC_OK == responseCode ) {
        return parseSearchResponse(query, method.getResponseBodyAsStream());
      }

      throw new SearchServiceException("Failed to process search response returned code " + responseCode );

    } catch( SearchServiceException ex ) {
      throw ex;
    } catch( Exception ex ) {
      logger.warn("Failed to query Open Search Service", ex);
      throw new SearchServiceException("Failed to query Open Search Service", ex);
    }

  }


  protected HttpMethod createRequest(Query query) {
    GetMethod request = new GetMethod(searchServiceUrl);
    request.setQueryString(createQueryString(query));
    return request;
  }


  protected String createQueryString(Query query) {
    StringBuilder queryStr = new StringBuilder();
    queryStr.append("q=").append(query.getSearchTerms().replace(" ", "+"));

    if( query.getStartPage() > 0 ) {
      queryStr.append("&p=").append(query.getStartPage());
    }

    if( query.getItemsPerPage() > 0 ) {
      queryStr.append("&c=").append(query.getItemsPerPage());
    }
    
    return queryStr.toString();
  }
  

  protected SearchResults parseSearchResponse(Query query, InputStream in) {

    try {
      ResultBuilder processor = new ResultBuilder(query);

      openSearchXMLParser.parse(in, processor);

      return processor.createResults();
    } catch( XMLStreamException ex ) {
      throw new SearchServiceException("Failed to parse response", ex); 
    }
  }

}
